package org.ediba.siri.core.image.service;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

import mediautil.gen.Log;
import mediautil.image.jpeg.LLJTran;
import mediautil.image.jpeg.LLJTranException;

import org.ediba.siri.core.Filter;
import org.ediba.siri.core.Util;
import org.ediba.siri.core.action.service.ActionService;
import org.ediba.siri.core.image.data.ImageAlbumDao;
import org.ediba.siri.core.image.data.ImageBackupDao;
import org.ediba.siri.core.image.data.ImageDao;
import org.ediba.siri.core.image.data.ImageKeywordDao;
import org.ediba.siri.core.image.data.ImageProviderDao;
import org.ediba.siri.core.image.domain.Image;
import org.ediba.siri.core.image.domain.ImageAlbum;
import org.ediba.siri.core.image.domain.ImageBackup;
import org.ediba.siri.core.image.domain.ImageKeyword;
import org.ediba.siri.core.image.domain.ImageProvider;
import org.ediba.siri.core.model.domain.Model;
import org.ediba.siri.core.model.domain.ModelVisit;
import org.ediba.siri.core.request.data.RequestDao;
import org.ediba.siri.core.request.domain.Request;
import org.ediba.siri.core.test.domain.Imagekeywordtemp;
import org.ediba.siri.core.user.data.GlobalDao;
import org.ediba.siri.core.user.domain.Global;
import org.ediba.siri.core.user.domain.User;
import org.ediba.siri.core.user.service.UserService;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.FileCopyUtils;

public class DefaultImageService implements ImageService {
	
	private ImageDao imageDao;
	public void setImageDao(ImageDao imageDao) {this.imageDao = imageDao;}
	
	private RequestDao requestDao;
	public void setRequestDao(RequestDao requestDao) {this.requestDao = requestDao;}
	
	private ImageAlbumDao imageAlbumDao;
	public void setImageAlbumDao(ImageAlbumDao imageAlbumDao) {this.imageAlbumDao = imageAlbumDao;}
	
	private ImageKeywordDao imageKeywordDao;
	public void setImageKeywordDao(ImageKeywordDao imageKeywordDao) {this.imageKeywordDao = imageKeywordDao;}
	
	private ImageProviderDao imageProviderDao;
	public void setImageProviderDao(ImageProviderDao imageProviderDao) {this.imageProviderDao = imageProviderDao;}
		
	private ImageBackupDao imageBackupDao;
	public void setImageBackupDao(ImageBackupDao imageBackupDao) {this.imageBackupDao = imageBackupDao;}
	
	private UserService userService;
	public void setUserService(UserService userService) {this.userService = userService;}	

	private GlobalDao globalDao;
	public void setGlobalDao(GlobalDao globalDao) {this.globalDao = globalDao;}
	
	private ActionService actionService;
	public void setActionService(ActionService actionService) {this.actionService = actionService;}
	
	private String buildThumbDir(Image image){
		return this.globalDao.findById("imageThumbDir").getValue() + "/" + new SimpleDateFormat("yyyy/MM/dd").format(image.getTimestamp());
	}
	
	private String buildMasterDir(Image image){
		return this.globalDao.findById("imageMasterDir").getValue() + "/" + new SimpleDateFormat("yyyy/MM/dd").format(image.getTimestamp());
	}
	
	/****************************IMAGE******************************/
	
	@Override
	public synchronized Image create(boolean requestType) {
		Name _name = this.getNextName(requestType);
		Image image = new Image(_name.toString(), Calendar.getInstance().getTime(),false,false);
		this.setNextName(requestType, this.incName(_name));
		image.setTagged(false);
		image.setCatalogged(false);
		image.setLockedDownload(true);
		image.setLockedSearch(true);
		this.imageDao.attachDirty(image);
		return image;
	}
	
	@Override
	public Image retrieve(int id) {
		return this.imageDao.findById(id);
	}
	
	@Override
	public Image update(Image image) {
		if (!image.isCatalogged())
			image.setCatalogged(true);
		this.imageDao.attachDirty(image);
		return image;
	}
	
	@Override
	public boolean delete(int id) {
		if (!Util.currentUserHasRole("ROLE_IMAGE_DELETE"))
			return false;
		
		// TODO: Validar la posibilidad de borrar los archivos del disco antes de borrar los registros en la base
		// Como estaba antes fallaba porque deleteMaster y deleteThumb recuperan la imagen de la base para 
		// armar la ruta del archivo
		try{
			this.deleteMaster(id);
			this.deleteThumb(id);
			return this.imageDao.delete(id);
		}catch (Exception e) {e.printStackTrace();}
		return false;
	}

	@Override
	public Image updateTimestamp(int id, Date timestamp) {
		final Image image = this.imageDao.findById(id);
		if (image == null) throw new IllegalArgumentException("La imagen no existe");
		
		Calendar cal1 = Calendar.getInstance();
		Calendar cal2 = Calendar.getInstance();
		cal1.setTime(timestamp);
		cal2.setTime(image.getTimestamp());
		if (cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) && cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR))
			return image;
		
        File[] filesThumb = new File(this.buildThumbDir(image)).listFiles(new FilenameFilter() { 
        	public boolean accept(File dir, String filename){
        		return filename.startsWith(image.getName());
        	}
        } );
        File[] filesMaster = new File(this.buildMasterDir(image)).listFiles(new FilenameFilter() { 
        	public boolean accept(File dir, String filename){
        		return filename.startsWith(image.getName());
        	}
        } );
	        
        image.setTimestamp(timestamp);
        
        File fileOut;
        String dir = this.buildThumbDir(image);
        new File(dir).mkdirs();
        for (File fileIn : filesThumb) {
        	fileOut = new File(dir + "/" + fileIn.getName()); 
			try {FileCopyUtils.copy(fileIn, fileOut);}
			catch (IOException e) {e.printStackTrace();}
			fileIn.delete();
		}
        dir = this.buildMasterDir(image);
        new File(dir).mkdirs();
        for (File fileIn : filesMaster) {
        	fileOut = new File(dir + "/" + fileIn.getName()); 
			try {FileCopyUtils.copy(fileIn, fileOut);}
			catch (IOException e) {e.printStackTrace();}
			fileIn.delete();		
		}
        
		return image;
	}

	@Override
	public List<Image> retrieve(Filter filter, Object value, String sort, boolean dir) {
		return this.imageDao.findByFilters(Util.simpleFilter(filter, value), sort, dir);
	}

	@Override
	public List<Image> retrieve(Filter filter, Object value, String sort, boolean dir, Integer start, Integer limit) {
		return this.imageDao.findByFilters(Util.simpleFilter(filter, value), sort, dir, start, limit);
	}

	@Override
	public List<Image> retrieve(Map<Filter, Collection<Object>> filters, String sort, boolean dir) {
		return this.imageDao.findByFilters(filters, sort, dir);
	}

	@Override
	public List<Image> retrieve(Map<Filter, Collection<Object>> filters, String sort, boolean dir, Integer start, Integer limit) {
		return this.imageDao.findByFilters(filters, sort, dir, start, limit);
	}

	@Override
	public List<Image> retrieve(List<Integer> ids) {
		if (ids.size()!=0)
			return this.imageDao.findByFilters(Util.simpleFilterOr(Filter.ID, ids), null, false);
		else return new ArrayList<Image>();
	}

	@Override
	public int retrieveCount(Map<Filter, Collection<Object>> filters) {
		return this.imageDao.findByFiltersCount(filters);
	}

	@Override
	public List<Image> retrieveUsed(int idModel) {
		return this.imageDao.findUsedByModel(idModel);
	}

	@Override
	public void massTagging(int id) {
		Request request = this.imageDao.findById(id).getRequest();
		if (request != null)
			for (Image image : request.getImages())
				if (!image.isTagged()){
					image.setTagged(true);
					this.imageDao.attachDirty(image);
				}
	}
	
	/****************************IMAGE-ALBUM******************************/
	
	@Override
	public ImageAlbum albumCreate() {
		User user = this.userService.retrieve(SecurityContextHolder.getContext().getAuthentication().getName());
		ImageAlbum imageAlbum = new ImageAlbum(user);
		this.imageAlbumDao.attachDirty(imageAlbum);
		return imageAlbum;
	}

	@Override
	public ImageAlbum albumRetrieve(int id) {
		return this.imageAlbumDao.findById(id);
	}

	@Override
	public ImageAlbum albumUpdate(ImageAlbum imageAlbum) {
		this.imageAlbumDao.attachDirty(imageAlbum);
		return imageAlbum;
	}

	@Override
	public boolean albumDelete(int id) {
		return this.imageAlbumDao.delete(id);
	}

	@Override
	public List<ImageAlbum> albumRetrieve() {
		User user = this.userService.retrieve(SecurityContextHolder.getContext().getAuthentication().getName());
		List<ImageAlbum> imageAlbums = this.imageAlbumDao.findByFilters(Util.simpleFilter(Filter.USER, user.getId()), null, false);
		return imageAlbums;	
	}

	/****************************IMAGE-KEYWORD******************************/
	
	@Override
	public ImageKeyword keywordCreate(String word) {
		ImageKeyword imageKeyword = new ImageKeyword(word);
		return this.imageKeywordDao.merge(imageKeyword);
	}

	@Override
	public ImageKeyword keywordRetrieve(int id) {
		return this.imageKeywordDao.findById(id);
	}

	@Override
	public ImageKeyword keywordUpdate(ImageKeyword imageKeyword) {
		return this.imageKeywordDao.merge(imageKeyword);
	}

	@Override
	public boolean keywordDelete(int id) {
		ImageKeyword imageKeyword = this.imageKeywordDao.findById(id);
		if (imageKeyword!=null){
			this.imageKeywordDao.delete(imageKeyword);
			return true;
		}
		else 
			return false;
	}

	@Override
	public List<ImageKeyword> keywordRetrieve(String sort, boolean dir, Integer start, int limit){
		if (start != null)
			return this.imageKeywordDao.getAll(sort, dir, start, limit);
		else
			return this.imageKeywordDao.getAll(sort, dir);
	}
	
	@Override
	public List<ImageKeyword> keywordRetrieve(String word, Integer start, int limit){
		if (start != null)
			return this.imageKeywordDao.findByFilters(Util.simpleFilter(Filter.WORD, word), "word", true, start, limit);
		else 
			return this.imageKeywordDao.findByFilters(Util.simpleFilter(Filter.WORD, word), "word", true);
	}
	
	@Override
	public List<ImageKeyword> keywordRetrieveByImage(int idImage) {
		return this.imageKeywordDao.findByFilters(Util.simpleFilter(Filter.IMAGE, idImage), "word", true);
	}
	
	/****************************IMAGE-PROVIDER******************************/
	
	@Override
	public ImageProvider providerCreate(String name) {
		ImageProvider imageProvider = new ImageProvider(name);
		return this.imageProviderDao.merge(imageProvider);
	}

	@Override
	public ImageProvider providerRetrieve(int id) {
		return this.imageProviderDao.findById(id);
	}

	@Override
	public ImageProvider providerUpdate(ImageProvider imageRrovider) {
		return this.imageProviderDao.merge(imageRrovider);
	}

	@Override
	public boolean providerDelete(int id) {
		ImageProvider imageProvider = this.imageProviderDao.findById(id);
		if (imageProvider!=null){
			this.imageProviderDao.delete(imageProvider);
			return true;
		}
		else
			return false;
	}

	@Override
	public List<ImageProvider> providerRetrieve(String sort, boolean dir, Integer start, int limit){
		if (start != null)
			return this.imageProviderDao.getAll(sort, dir, start, limit);
		else
			return this.imageProviderDao.getAll(sort, dir);
	}
	
	@Override
	public List<ImageProvider> providerRetrieve(String name, Integer start, int limit){
		if (start != null)
			return this.imageProviderDao.findByFilters(Util.simpleFilter(Filter.NAME, name), "name", true, start, limit);
		else 
			return this.imageProviderDao.findByFilters(Util.simpleFilter(Filter.NAME, name), "name", true);
	}

	/****************************IMAGE-BACKUP******************************/
	
	@Override
	public ImageBackup backupCreate(String name) {
		ImageBackup imageBackup = new ImageBackup(name);
		return this.imageBackupDao.merge(imageBackup);
	}

	@Override
	public ImageBackup backupRetrieve(int id) {
		return this.imageBackupDao.findById(id);
	}

	@Override
	public ImageBackup backupUpdate(ImageBackup imageBackup) {
		return this.imageBackupDao.merge(imageBackup);
	}

	@Override
	public boolean backupDelete(int id) {
		ImageBackup imageBackup = this.imageBackupDao.findById(id);
		if (imageBackup!=null){
			this.imageBackupDao.delete(imageBackup);
			return true;
		}
		else
			return false;
	}

	@Override
	public List<ImageBackup> backupRetrieve(String sort, boolean dir, Integer start, int limit){
		if (start != null)
			return this.imageBackupDao.getAll(sort, dir, start, limit);
		else
			return this.imageBackupDao.getAll(sort, dir);
	}
	
	@Override
	public List<ImageBackup> backupRetrieve(String name, Integer start, int limit){
		if (start != null)
			return this.imageBackupDao.findByFilters(Util.simpleFilter(Filter.NAME, name), "name", true, start, limit);
		else 
			return this.imageBackupDao.findByFilters(Util.simpleFilter(Filter.NAME, name), "name", true);
	}

	/********************************************************************/

	@Override
	public void updateThumb(int id, InputStream inputStream, String type) {
		FileOutputStream fileOutputStream = null;
		Image image = this.imageDao.findById(id);
		String dir = this.buildThumbDir(image);
		new File(dir).mkdirs();
		
		try {fileOutputStream = new FileOutputStream(dir + "/" + image.getName() + "." + type);
		} catch (FileNotFoundException e) {e.printStackTrace();}
		try {FileCopyUtils.copy(inputStream, fileOutputStream);
		} catch (IOException e) {e.printStackTrace();}
	}

	@Override
	public void updateMaster(int id, InputStream inputStream, String type) {
		FileOutputStream fileOutputStream = null;
		Image image = this.imageDao.findById(id);
		String dir = this.buildMasterDir(image);
		new File(dir).mkdirs();
		
		try {fileOutputStream = new FileOutputStream(dir + "/" + image.getName() + "." + type);
		} catch (FileNotFoundException e) {e.printStackTrace();}
		try {FileCopyUtils.copy(inputStream, fileOutputStream);
		} catch (IOException e) {e.printStackTrace();}	

	}
	
	private void deleteThumb(int id) {
		final Image image = this.imageDao.findById(id);
		
        File[] files = new File(this.buildThumbDir(image)).listFiles(new FilenameFilter() { 
        	public boolean accept(File dir, String filename){
        		return filename.startsWith(image.getName());
        	}
        } );
		
        for (File file : files) 
			file.delete();
	}

	@Override
	public void deleteMaster(int id, String type) {
		Image image = this.imageDao.findById(id);
		new File(this.buildMasterDir(image) + "/" + image.getName() + "." + type).delete();
	}
	
	@Override
	public void deleteMaster(int id) {
		final Image image = this.imageDao.findById(id);
		
        File[] files = new File(this.buildMasterDir(image)).listFiles(new FilenameFilter() { 
        	public boolean accept(File dir, String filename){
        		return filename.startsWith(image.getName());
        	}
        } );
        
        for (File file : files) 
			file.delete();
	}

	@Override
	public Map<String,InputStream> retrieveThumb(int id) {
		final Image image = this.imageDao.findById(id);
		
        File[] files = new File(this.buildThumbDir(image)).listFiles(new FilenameFilter() { 
        	public boolean accept(File dir, String filename){
        		return filename.startsWith(image.getName());
        	}
        } );

        if (files!=null)
	        if (files.length == 0)
	        	return null;
	        else {
	            Map<String,InputStream> result = new HashMap<String,InputStream>();
	            try {result.put(Util.getFileExtension(files[0].getName()),new FileInputStream(files[0]));
				} catch (FileNotFoundException e) {e.printStackTrace();}
	            return result;
	        }
        else return null;
	}

	@Override
	public List<String> retrieveMasterTypes(int id) {
					
		final Image image = this.imageDao.findById(id);
		File[] files = new File[0];
		
		File _dir = new File(this.buildMasterDir(image));
		if (_dir.exists()){
	        files = _dir.listFiles(new FilenameFilter() { 
	        	public boolean accept(File dir, String filename){
	        		return filename.startsWith(image.getName());
	        	}
	        } );
		}
        
    	List<String> result = new ArrayList<String>(files.length);
    	for (File file : files)
			result.add(Util.getFileExtension(file.getName()));
			
        return result;
	}
	
	@Override
	public Map<String,InputStream> retrieveMaster(int id) {
		
		if (this.actionService.download(id, this.actionService.canForceDownload(this.userService.retrieveCurrent())) != ActionService.ERROR_OK)
			return new HashMap<String,InputStream>(0);
			
		final Image image = this.imageDao.findById(id);
		File[] files = new File[0];
		
		File _dir = new File(this.buildMasterDir(image));
		if (_dir.exists()){
	        files = _dir.listFiles(new FilenameFilter() { 
	        	public boolean accept(File dir, String filename){
	        		return filename.startsWith(image.getName());
	        	}
	        } );
		}
        
    	Map<String,InputStream> result = new HashMap<String,InputStream>(files.length);
    	for (File file : files)
            try {result.put(Util.getFileExtension(file.getName()),new FileInputStream(file));
			} catch (FileNotFoundException e) {e.printStackTrace();}
			
        return result;
	}
	
	@Override
	public InputStream retrieveMaster(int id, String type) {
		
		final Image image = this.imageDao.findById(id);
		final String _type = type;
		File[] files = new File[0];
		InputStream _result = null;
		
		File _dir = new File(this.buildMasterDir(image));
		if (_dir.exists()){
	        files = _dir.listFiles(new FilenameFilter() { 
	        	public boolean accept(File dir, String filename){
	        		return filename.startsWith(image.getName()) && filename.endsWith("." + _type);
	        	}
	        } );
		}
        
		if (files.length == 1)
			try {_result = new FileInputStream(files[0]);}
			catch (FileNotFoundException e) {e.printStackTrace();}
		
		return _result;
	}
	
	@Override
	public Name getNextName(boolean requestType) {
		return new Name(
			this.globalDao.findById(requestType ? GlobalDao.IMAGE_PHOTO_LETTER_KEY : GlobalDao.IMAGE_DRAW_LETTER_KEY).getValue().charAt(0),
			Integer.parseInt(this.globalDao.findById(requestType ? GlobalDao.IMAGE_PHOTO_NUMBER_KEY : GlobalDao.IMAGE_DRAW_NUMBER_KEY).getValue())
		);
	}
	
	@Override
	public void setNextName(boolean requestType, Name name) {
		Global globalLetter = this.globalDao.findById(requestType ? GlobalDao.IMAGE_PHOTO_LETTER_KEY : GlobalDao.IMAGE_DRAW_LETTER_KEY);
		Global globalNumber = this.globalDao.findById(requestType ? GlobalDao.IMAGE_PHOTO_NUMBER_KEY : GlobalDao.IMAGE_DRAW_NUMBER_KEY);
		globalLetter.setValue(String.valueOf(Character.toUpperCase(name.letter)));
		globalNumber.setValue(String.valueOf(name.number));	
		this.globalDao.attachDirty(globalLetter);
		this.globalDao.attachDirty(globalNumber);
	}
	
	private Name incName(Name name) {
		Name _name = new Name(name.letter,name.number);
		_name.number++;
		if (name.number > 9999999){
			_name.number = 0;
			_name.letter++;
			if (_name.letter > 'Z')
				_name.letter = 'A';
		}
		return _name;
	}

	@Override
	public List<ImageKeyword> keywordRetrieve() {
		return this.imageKeywordDao.getAll(null, false);
	}

	@Override
	public List<ImageKeyword> keywordRetrieveByKeyword(int idImageKeyword) {
		return new ArrayList<ImageKeyword>(this.imageKeywordDao.findById(idImageKeyword).getImagekeywordsForIdImageKeywordB());
	}

	@Override
	public void rotateThumb(int id, boolean dir){
		
		final Image image = this.imageDao.findById(id);
		File[] files = new File(this.buildThumbDir(image)).listFiles(new FilenameFilter() { 
        	public boolean accept(File dir, String filename){
        		return filename.startsWith(image.getName());
        	}
        } );
    	if (files.length == 0)
        	return;
        
        // Raise the Debug Level which is normally LEVEL_INFO. Only Warning  
        // messages will be printed by MediaUtil.  
        Log.debugLevel = Log.LEVEL_WARNING;  
  
        // 1. Initialize LLJTran and Read the entire Image including Appx markers  
        LLJTran llj = new LLJTran(files[0]);  
        // If you pass the 2nd parameter as false, Exif information is not  
        // loaded and hence will not be written.  
        try {llj.read(LLJTran.READ_ALL, true);
		} catch (LLJTranException e) {e.printStackTrace();}  
  
        // 2. Transform the image using default options along with  
        // transformation of the Orientation tags. Try other combinations of  
        // LLJTran_XFORM.. flags. Use a jpeg with partial MCU (partialMCU.jpg)  
        // for testing LLJTran.XFORM_TRIM and LLJTran.XFORM_ADJUST_EDGES  
        int options = LLJTran.OPT_DEFAULTS | LLJTran.OPT_XFORM_ORIENTATION;  
        
        int op;
        if (dir)
        	op = LLJTran.ROT_90;
        else 
        	op = LLJTran.ROT_270;
        llj.transform(op, options);  
  
        // 4. Save the Image which is already transformed as specified by the  
        //    input transformation in Step 2, along with the Exif header.  
        OutputStream out = null;
        
        try {out = new BufferedOutputStream(new FileOutputStream(files[0]));
		} catch (FileNotFoundException e) {e.printStackTrace();}  
        
		try {llj.save(out, LLJTran.OPT_WRITE_ALL);
		} catch (IOException e) {e.printStackTrace();}
		
        try {out.close();
		} catch (IOException e) {e.printStackTrace();}  
        
		// Cleanup  
        llj.freeMemory();  	
	}

	@Deprecated
	@Override
	public List<Imagekeywordtemp> imageKeywordTempRetrieve() {
		return this.imageKeywordDao.getImageKeywordtemp();
	}

	@Override
	public Image updateRequest(int id, int idRequest) {
		Request _request = this.requestDao.findById(idRequest);
		Image _image = this.imageDao.findById(id);
		if (_image.getRequest() == null && _request != null ){
			_image.setCatalogged(false);
			_image.setTagged(false);
			_image.setLockedDownload(false);
			_image.setLockedSearch(false);
			List<ModelVisit> modelvisits = new ArrayList<ModelVisit>(_request.getModelvisits());
			List<Model> models = new ArrayList<Model>(modelvisits.size());
			for (ModelVisit modelvisit : modelvisits)
				models.add(modelvisit.getModel());
			_image.setModels(new HashSet<Model>(models));
		}
		_image.setRequest(_request);
		return _image;
	}

}
